/*
 * GraphService.java
 *
 * This file is part of FUJIWARA.
 * Copyright (C) 2010 Loris Fichera    (loris.fichera@gmail.com)
 *                    Daniele Marletta (danielemar86@gmail.com)
 *
 * FUJIWARA is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FUJIWARA is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FUJIWARA.  If not, see <http://www.gnu.org/licenses/>.
 */

package GraphService;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import java.util.ArrayList;
import java.lang.Math;
import java.lang.Integer;
import java.lang.Double;

/**
 *
 * @author Loris Fichera
 * @author Daniele Marletta
 *
 */
@WebService()
public class GraphService {

    private static final String IC = "IC";
    private static final String MC = "MC";
    private static final String C  = "C";
    private static final String PC = "PC";
    private static final String SC = "SC";

    /** Implements the Knuth algorithm */
    private static double standardDeviation(int [] population) {

        long n = 0;
        double mean = 0;
        double s = 0.0;

        for (double x : population) {
                n++;
                double delta = x - mean;
                mean += delta / n;
                s += delta * (x - mean);
        }

        return Math.sqrt(s / n);
    }

    
    @WebMethod()
    public int [] countNeighbours (@WebParam() int [] adjVector) {

        //convert adj vector to adj matrix
        Double dSize = new Double(adjVector.length);
        dSize = Math.sqrt(dSize);
        int size = dSize.intValue();

        int [][] adjMatrix = new int[size][size];

        int k = 0;
        for (int i = 0; i < size; i ++) {
            for ( int j = 0; j < size; j ++) {
                adjMatrix[i][j] = adjVector[k];
                k++;
            }
        }

        // initialize connections vector
        int [] neighboursCount = new int [size];
        for (int i = 0; i < size; i++)
            neighboursCount[i] = 0;

        // for each node
        // count connections
        for (int i = 0; i < size; i ++)
            for ( int j = 0; j < size; j ++)
                if (adjMatrix[i][j] == 1)
                    neighboursCount[i]++;
        
        return neighboursCount;
    }

    @WebMethod()
    public String [] processConnections (@WebParam() int [] neighboursCount) {

        double icTh;
        double mcTh;
        double cTh;
        double pcTh;
        double scTh;
        double neighAverage = 0;
        double stdDeviation = 0;

        // calculate average
        for (int i = 0; i < neighboursCount.length; i++)
            neighAverage += neighboursCount[i];

        neighAverage = neighAverage / neighboursCount.length;
        stdDeviation = standardDeviation (neighboursCount);
        
        // calculate thresholds
        icTh = neighAverage + 1.5 * stdDeviation;
        mcTh = neighAverage + (30 / 100 ) * stdDeviation * 1.5;
        cTh  = neighAverage - (30 / 100 ) * stdDeviation * 1.5;
        pcTh = neighAverage - 1.5 * stdDeviation;

        // calculate classes
        String [] partitionVector = new String [neighboursCount.length];

        for (int i = 0; i < partitionVector.length; i++) {

            if ( neighboursCount[i] >= icTh )
                partitionVector[i] = IC;

            else if ( neighboursCount[i] < icTh && neighboursCount[i] >= mcTh )
                partitionVector[i] = MC;

            else if ( neighboursCount[i] < mcTh && neighboursCount[i] >= cTh )
                partitionVector[i] = C;

            else if ( neighboursCount[i] < cTh && neighboursCount[i] >= pcTh )
                partitionVector[i] = PC;

            else if ( neighboursCount[i] < pcTh )
                partitionVector[i] = SC;
        }
        return partitionVector;
    }

        /**
     * Compute, for every node of the net, the number of hops from
     * an hyperconnected node.
     * NOTE: in the following, the term 'hypernode' is used as a synonim
     * of hyperconnected node.
     *
     * @param adjVector is the adjacency matrix vectorialized.
     * @param neighbourCount is an array that, for every index, contains
     * the total number of neighbours of the corresponding node.
     * @param hyperNodeIndex is the index of the neighbourCount where the
     * hypernode is located.
     * @return  the propagation vector which, for every index, contains the number
     * of hops of the node from the hyperNode.
     */
    @WebMethod()
    public int [] computeHops ( @WebParam() int [] adjVector,
                                @WebParam() int [] neighbourCount,
                                @WebParam() int hyperNodeIndex) {


        Double dSize = new Double(adjVector.length);
        dSize = Math.sqrt(dSize);
        /** Total number of nodes in the network. */
        final int SIZE = dSize.intValue();

        /** Rebuild the adjacency Matrix. */
        int [][] adjMatrix = new int[SIZE][SIZE];
        int k = 0;
        for (int i = 0; i < SIZE; i ++) {
            for ( int j = 0; j < SIZE; j ++) {
                adjMatrix[i][j] = adjVector[k];
                k++;
            }
        }

        /** Build the array of Node objects. */
        Node [] nodes = new Node [SIZE];

        int clusterID = 0;
        for (int index = 0; index < SIZE; index++)  {
            if ( index == hyperNodeIndex)
            {
                nodes[index] = new HyperNode(neighbourCount[index], new Integer(++clusterID).toString());

            } else {
                nodes[index] = new Node(neighbourCount[index]);
            }
        }

        /**
         * Rebuild the topology of the node by using Node objects.
         */
        Node currentNode;
        for (int row=0; row<SIZE; row++){
            currentNode = nodes[row];
            for(int column=0; column<SIZE; column++){
                if (adjMatrix[row][column] == 1){
                    currentNode.addNeighbour(nodes[column]);
                }
            }
        }


        Node hyperNode = nodes[hyperNodeIndex];
        /** Execute the algorithm. */
        hyperNode.propagate();

        int [] propagationArray = new int [SIZE];

        
        for (int i=0; i<SIZE; i++){
            Integer hops = nodes[i].getHops();
            propagationArray[i] = hops;
        }

        return propagationArray;
    }
}

/**
 *
 *  The generic Node of the network. Every Node holds
 *  a reference to its neighbours and keeps track of
 *  the number of hops from a particular node.
 */
class Node {

    protected int totalNeighbours;
    protected int neighboursCounter;
    protected int hops;
    protected Node [] neighbours;
    protected String clusterName;


    public Node(int neighbourCount) {
        this(neighbourCount,null,-1);
    }

    public Node(int neighbourCount, String clusterName) {
        this(neighbourCount,clusterName,-1);
    }

    public Node(int neighbourCount, String clusterName, int hops) {

        this.totalNeighbours = neighbourCount;
        neighbours = new Node [totalNeighbours];
        neighboursCounter = 0;

        this.clusterName = clusterName;
        this.hops = hops;
    }


    public void addNeighbour(Node neighbour){
        try {
            if (neighboursCounter < totalNeighbours ) {
                neighbours[neighboursCounter] = neighbour;
                neighboursCounter++;
            } else {
                throw new InconsistentNeighboursException();
            }
        } catch (InconsistentNeighboursException e) {
            System.out.println("Error! " + e.getMessage());

        }
    }


    /** This method should be meaningfully implemented by subclasses. */
    public void propagate() {}


    /** Getters, setters and utility methods.   */

    public boolean isReachable() {
        return (hops >= 0);
    }

    public int getHops() {
        return hops;
    }

    public void setHops(int hops) {
        this.hops = hops;
    }

    public String getClusterName() {
        return clusterName;
    }

    public void setClusterName(String name){
        clusterName = name;
    }

    public Node[] getNeighbours() {
        return neighbours;
    }

}


/**
 * Hyperconnected Node of the network. The clustering algorithm
 * computes the hops of all nodes from a particular HyperNode.
 *
 */
class HyperNode extends Node {

    /** By using FIFO order, every Node in this list
     *  must set the hop of all its neighbours.
     */
    private static ArrayList<Node> pendingNodes;


    public HyperNode(int neighbourCount, String clusterName) {
        super(neighbourCount,clusterName,0);

        pendingNodes = new ArrayList<Node>();

    }

    /** Implements the algorithm to compute the hops of all
     * other nodes of the network from the current hypernode.
     */
    @Override
    public void propagate() {
        /** Initialize the list. */
        pendingNodes.add(this);

        while(pendingNodes.size() > 0)
        {
            /** Pop the next node to be processed. */
            Node parentNode = pendingNodes.get(0);
            pendingNodes.remove(0);
            /** Retrieve the neighbours of the node. */
            Node[] children = parentNode.getNeighbours();
            for (int i=0; i< children.length; i++) {
                Node childNode = children[i];
                if (! childNode.isReachable() ) {
                    childNode.setHops ( parentNode.getHops() + 1);
                    pendingNodes.add(childNode);
                }
            }
        }

    }
}


/**
 *  This exception is raised when the number of the neighbours of a Node
 *  object is different from the number declared in the input array of
 * the classify() method.
 */
class InconsistentNeighboursException extends Exception {

    private String message;

    InconsistentNeighboursException() {
        message = "Attempt to add a neighbour so that the number of neighbours exceeds the declared total number of neighbours";
    }

    @Override
    public String getMessage() {
        return message;
    }
}

